#include "NextBIP.h"
#include <algorithm>
#include <set>
#include <vector>
#include <map>
#include "PKB.h"
#include <iostream>

NextBIP::NextBIP()
{
}
NextBIP::~NextBIP()
{
}
void NextBIP::computeNextBIP()
{
	PROG_LINE_ARRAY callStmt = PKB::shortcutTable.GetAllCall();
	nextBIP_store = PKB::next.next_store;
	prevBIP_store = PKB::next.prev_store;

	for(auto it=callStmt.begin();it!=callStmt.end();it++)
	{
		PROCINDEX procIndex = PKB::calls.GetCalleeByStatement(*it);
		PROG_LINE callFirstStmt = PKB::procTable.GetProcFirstStatementNumber(procIndex);
		PROG_LINE_ARRAY callLastStmt = getLastStatement(procIndex);//PKB::procTable.GetProcLastStatementNumber(procIndex);
		PROG_LINE_ARRAY nextStmtArray = PKB::next.GetNext(*it);
		PROG_LINE_ARRAY::iterator nextStmt;
		nextStmt = nextStmtArray.begin();
		if(nextStmt != nextStmtArray.end()){
			// Erase old relations
			this->nextBIP_store[*it].erase(*nextStmt);
			this->prevBIP_store[*nextStmt].erase(*it);
			for(auto it1=callLastStmt.begin();it1!=callLastStmt.end();it1++){
				if (nextBIP_store.find(*it1) != nextBIP_store.end())
					this->nextBIP_store[*it1].insert(*nextStmt);
				else{
					PROG_LINE_ARRAY next_temp;
					next_temp.insert(*nextStmt);
					this->nextBIP_store.insert(pair<PROG_LINE,PROG_LINE_ARRAY>(*it1,next_temp));
				}		
				if (prevBIP_store.find(*nextStmt) != prevBIP_store.end())
					this->prevBIP_store[*nextStmt].insert(*it1);
				else{
					PROG_LINE_ARRAY prev_temp;
					prev_temp.insert(*it1);
					this->prevBIP_store.insert(pair<PROG_LINE,PROG_LINE_ARRAY>(*nextStmt,prev_temp));
				}
			}
		}
		
		if (nextBIP_store.find(*it) != nextBIP_store.end())
			this->nextBIP_store[*it].insert(callFirstStmt);
		else{
			PROG_LINE_ARRAY next_temp1;
			next_temp1.insert(callFirstStmt);
			this->nextBIP_store.insert(pair<PROG_LINE,PROG_LINE_ARRAY>(*it,next_temp1));
		}
		if (prevBIP_store.find(callFirstStmt) != prevBIP_store.end())
			this->prevBIP_store[callFirstStmt].insert(*it);
		else{
			PROG_LINE_ARRAY prev_temp1;
			prev_temp1.insert(*it);
			this->prevBIP_store.insert(pair<PROG_LINE,PROG_LINE_ARRAY>(callFirstStmt,prev_temp1));
		}
	}
}
BOOLEAN NextBIP::IsTransitiveNextBIP(PROG_LINE prev,PROG_LINE next)
{
	PROG_LINE_ARRAY prev_result;
	prev_result = NextBIP::GetTransitiveNextBIP(prev);
	if (prev_result.find(next) != prev_result.end())
		return true;
	return false;
}

PROG_LINE_ARRAY NextBIP::GetNextBIP(PROG_LINE prev)
{
	PROG_LINE_ARRAY result;
	result = this->nextBIP_store[prev];
	return result;
}
PROG_LINE_ARRAY NextBIP::GetPreviousBIP(PROG_LINE next)
{
	PROG_LINE_ARRAY result;
	result = this->prevBIP_store[next];
	return result;
}
BOOLEAN NextBIP::IsNextBIP(PROG_LINE prev,PROG_LINE next)
{
	PROG_LINE_ARRAY temp;
	temp = this->nextBIP_store[prev];
	if(temp.find(next) != temp.end())
		return true;
	return false;
}
PROG_LINE_ARRAY NextBIP::GetTransitiveNextBIP(PROG_LINE prev)
{
	PROG_LINE_ARRAY result;
	PROCINDEX_ARRAY procArray;
    PROG_LINE_ARRAY temp,temp1;
    set<PROG_LINE>::iterator check;
    temp.insert(prev);
	procArray.insert(PKB::procTable.GetProcIndex(prev));
	if(PKB::shortcutTable.GetStatementType(prev)==CALL)
		procArray.insert(PKB::calls.GetCalleeByStatement(prev));
    do{
        temp = NextBIP::HelperGetTransitive(temp,1);
        // Delete items that i have searched before
        temp1.clear();
        for(auto i=temp.begin();i!=temp.end();i++){
			if(PKB::shortcutTable.GetStatementType(*i)==CALL)	
				procArray.insert(PKB::calls.GetCalleeByStatement(*i));
			if(procArray.find(PKB::procTable.GetProcIndex(*i))!=procArray.end() || procArray.size()==1){
				if(result.find(*i) == result.end())
					temp1.insert(*i);
			}
		}
        temp = temp1;
        set_union(temp.begin(),temp.end(),result.begin(),result.end(),inserter(result,result.begin()));
        // Prevent from searching from itself again
        check = temp.find(prev);
        if (check != temp.end())
			temp.erase(check);
    }while (temp.size() !=0);
    return result;
}

PROG_LINE_ARRAY NextBIP::GetTransitivePreviousBIP(PROG_LINE next)
{
	PROG_LINE_ARRAY result;
	PROCINDEX_ARRAY procArray;
    PROG_LINE_ARRAY temp,temp1;
    set<PROG_LINE>::iterator check;
	temp.insert(next);
	procArray.insert(PKB::procTable.GetProcIndex(next));
	PROG_LINE_ARRAY prev = PKB::next.GetPrevious(next);
	if(prev.size() == 1 && PKB::shortcutTable.GetStatementType(*prev.begin()))
		procArray.insert(PKB::calls.GetCalleeByStatement(*prev.begin()));
	do{
		temp = NextBIP::HelperGetTransitive(temp,2);
		// Delete items that i have searched before
		temp1.clear();
		for(auto i=temp.begin();i!=temp.end();i++){
			if(procArray.find(PKB::procTable.GetProcIndex(*i))!=procArray.end()){
				if(result.find(*i) == result.end())
					temp1.insert(*i);
				PROG_LINE_ARRAY prevStmt = PKB::next.GetPrevious(*i);
				if(prevStmt.size() == 1 && PKB::shortcutTable.GetStatementType(*prevStmt.begin())==CALL)	
					procArray.insert(PKB::calls.GetCalleeByStatement(*prevStmt.begin()));
			}
		}
		temp = temp1;
		set_union(temp.begin(),temp.end(),result.begin(),result.end(),inserter(result,result.begin()));
		// Prevent from searching from itself again
		check = temp.find(next);
		if (check != temp.end())
			temp.erase(check);
	}while (temp.size() !=0);
	return result;
}

PROG_LINE_ARRAY NextBIP::getLastStatement(PROCINDEX pi)
{
	PROG_LINE_ARRAY res,pl,temp;
	pl.insert(PKB::procTable.GetProcFirstStatementNumber(pi));
	PROG_LINE_ARRAY searchBefore;
	while(true)
	{
		for(auto it=pl.begin();it!=pl.end();it++)
		{
			if(searchBefore.find(*it) == searchBefore.end()){
				temp = PKB::next.GetNext(*it);
				if (temp.size() == 0)
					res.insert(*it);
				else{
					//temp.insert(*it);
					searchBefore.insert(*it);
				}
			}
		}
		pl.clear();
		pl = temp;
		temp.clear();
		if(pl.size() == 0)
			return res;
	}
}
set<PROG_LINE> NextBIP::HelperGetTransitive(set<PROG_LINE> input,int operation){
	PROG_LINE_ARRAY temp;
	PROG_LINE_ARRAY temp1;
	vector<PROG_LINE> store(this->nextBIP_store.size());
	vector<PROG_LINE> newResult(this->nextBIP_store.size());

	for(auto it=input.begin();it!=input.end();it++){
		if (operation == 1)
			temp = NextBIP::GetNextBIP(*it);
		else
			temp = NextBIP::GetPreviousBIP(*it);
		set_union(temp1.begin(),temp1.end(),temp.begin(),temp.end(),store.begin());
		temp1 = PKB::next.VectorToSet(store);
	}
	return temp1;
}